package affd.logic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
//import java.io.*;

public class NameAnonymizer implements Anonymizer {

	private static final int LEVEL_LOOSE = 1, /*LEVEL_MODERATE = 2,*/ LEVEL_STRICT = 3;
	
	private NameReplacement replacer;
	
	private HashSet<String> deleteList;
	private HashSet<String> ignoreList;

	private int level;
	private HfstStarter hfstStarter;

	
	/**
	 * Constructor for NameAnonymizer
	 * 
	 * @param r NameReplacement for use
	 * @param hfst HfstStarter for use
	 * @param strictness The level which indicates that how strict the program should be
	 * when deleting names. With level 1, all the possible names are anonymized. With level 2
	 * only most certainly names are anonymized. With level 3 nothing is anonymized directly.
	 */
	public NameAnonymizer(NameReplacement r, HfstStarter hfst, int strictness) {
		hfstStarter = hfst;
		level = strictness;
		replacer = r;
	}	
	
	
	/**
	 * Sets the deleteList and donNotDeleteList which are used by the NameAnonymizer.
	 * 
	 * @param delete The deleteList
	 * @param ignore The donNotDeleteList 
	 */
	public void setDeleteAndAnonymizeLists(HashSet<String> delete, HashSet<String> ignore) {
		this.deleteList = delete;
		this.ignoreList = ignore;
	}
	
	@Override
	public void anonymize(Token token) {		
	}

	@Override
	public void process(ArrayList<Sentence> sentences) throws LogicException {
		// Forming the list of names and possible names
		NameExtractor extractor = new NameExtractor(hfstStarter, deleteList);
		HashMap<String, ArrayList<Token>> candidates = extractor.processSentences(sentences);
			
		replacer.setHfstStarter(hfstStarter);
			
		HashMap<String, ArrayList<Token>> uncertains = processWordsAndReturnUncertains(candidates);
			
		for (ArrayList<Token> tokens : uncertains.values()) {
			markAsUncertain(tokens);
		}
	}
	
	/**
	 * Marks all the Tokens in the given ArrayList to be Uncertain
	 * 
	 * @param values
	 */
	private void markAsUncertain(ArrayList<Token> values) {
		String groupId = Token.generateGroupId();
		for (Token token : values) {
			token.uncertainContent(NameReplacementFI.MODIFICATION_CLASS, groupId);		
		}
	}
	
	/**
	 * Checks through the given set of possible names (name candidates). If the name candidate is in the
	 * middle of a sentence, it is most certainly a name and thus it is anonymized. If the word is not a known word,
	 * it is most probably a name. All the name candidates which cannot be confirmed to be certainly names
	 * are added to an ArrayList<String> which is returned by the method.
	 * 
	 * @param candidates HashMap which contains the name candidates
	 * @return ArrayList which contains the uncertain words which couldn't be confirmed to be
	 * most probably names.
	 */
	public HashMap<String, ArrayList<Token>> 
			processWordsAndReturnUncertains(HashMap<String, ArrayList<Token>> candidates) throws LogicException {
			
		HashMap<String, ArrayList<Token>> uncertainWords = new HashMap<String, ArrayList<Token>>();
		
    	for (String key : candidates.keySet()) {
			if (ignoreList != null && ignoreList.contains(key.toLowerCase())) {
				continue;
			}
			
			ArrayList<Token> tokenlist = candidates.get(key);	
			
			boolean isAddress = replacer.isAddress(key.toLowerCase());
			
			if (isAddress) {
				replacer.replaceAsAddress(key, candidates.get(key));
			} else if (deleteList != null && deleteList.contains(key.toLowerCase())) {
				replaceWord(key, tokenlist);
			} else {
				boolean tokenInMiddle = isInTheMiddleOfASentence(tokenlist);
				
				 // If the word certainly is a name (it is not a known word), replace it. If it's still uncertain
				 // whether the word is a name or not, add it to the hash table of uncertain
				 // cases.
				if (level != LEVEL_STRICT && (tokenInMiddle || level == LEVEL_LOOSE)) {
					replaceWord(key, tokenlist);
				} else {
					uncertainWords.put(key, candidates.get(key));
				}
			}
		}
    	
		return uncertainWords;
	}


	/**
	 * Calls the chosen replacer to replace all the instances of the given word.
	 * 
	 * @param wordString The basic form of the name of which instances are to be replaced
	 * @param wordsToReplace The ArrayList containing all the Tokens which should be replaced
	 */
	private void replaceWord(String wordString, ArrayList<Token> wordsToReplace) {
		replacer.replaceAll(wordString, wordsToReplace);
	}

	/**
	 * Checks if any of the given tokens is found to be in the middle of the sentence. If one is 
	 * found in the middle of the sentence, the word is most probably a name.
	 * 
	 * @param ArrayList of tokens of which indexes in the sentences are checked.
	 * @return Boolean value which tells if one of the tokens was found to be in the middle of
	 * a sentence.
	 */
    private boolean isInTheMiddleOfASentence(ArrayList<Token> tokens) {
    	// Test each token
    	for (Token token : tokens) {
    		Sentence sentence = token.getParent();
    		ArrayList<Token> sentenceTokens = sentence.getTokens();
    		
    		// Get token index
    		int index = sentenceTokens.indexOf(token);
    		
    		// Remove white spaces
    		int firstWord = 0;
    		while(firstWord < sentenceTokens.size() && sentenceTokens.get(firstWord).getType() == Token.Type.WHITE_SPACE) {
    			firstWord++;
    		}
    		
    		if (index > 0 && index > firstWord && sentenceTokens.get(index - 1).getType() == Token.Type.WHITE_SPACE) {
    			return true;
    		}
            
    	}  	
    	return false;
    }
}

